package main;

import java.util.ArrayList;

import javax.management.loading.MLet;

import utils.FileUtils;
import utils.MatrixLoader;

import matrix.MatricesMultiplierWithBarrier;
import matrix.MatrixMultiplyThread;
import barrier.*;
import barrier.senseBarrier.*;
import barrier.simpleBarrier.SimpleBarrier;
import counter.*;

/**
 * @author roei
 * 
 */
public class GeneralUtils {

    /**
     * Creates, starts and joins NUM_OF_THREADS threads
     * 
     * @param numOfThreads
     * @param barrierType
     * @param isLocalMem
     * @return
     */
    public static String startThreads(int numOfThreads,
            BarrierType barrierType, boolean isLocalMem, boolean isCounter) {
        int order = numOfThreads;
        if (!isCounter) {
            order = MatrixLoader.getInstance().getOrder();
            numOfThreads = order * order;
        }
        Thread threads[] = new Thread[numOfThreads];
        CounterWithBarrier sharedCounter = null;
        MatricesMultiplierWithBarrier matMult = null;
        int idx;
        if (isCounter) {
            sharedCounter = new CounterWithBarrier(barrierType, numOfThreads);
        } else {
            matMult = new MatricesMultiplierWithBarrier(
                    MatrixLoader.getInstance().getMatrices(), order,
                    getBarrier(barrierType, numOfThreads));
        }

        for (idx = 0; idx < order; idx++) {
            if (isCounter) {
                threads[idx] = isLocalMem ? new LocalMemWorkerThread(
                        sharedCounter) : new SyncCounterThread(sharedCounter);
            } else {
                for (int col = 0; col < order; col++) {
                    threads[(idx * order) + col] = new MatrixMultiplyThread(order, matMult, 
                            idx % order, col);
                }
            }
        }

        long startTime = System.nanoTime();
        // Start threads
        for (idx = 0; idx < numOfThreads; idx++) {
            threads[idx].start();
        }

        try {
            // Join threads
            for (idx = 0; idx < numOfThreads; idx++) {
                threads[idx].join(30000); // give threads 30sec to exit. XXX not
                                          // working?
            }
        } catch (InterruptedException e) {
            System.out
                    .println("Cought InterruptedException while joining threads,"
                            + " messsage is: " + e.getMessage());
            e.printStackTrace();
        }
        long totalTimeInMilli = (new Long(System.nanoTime() - startTime));// /
                                                                          // 1000000;

        String msg = barrierType
                + ", " + numOfThreads + " threads, finished after, "
                + totalTimeInMilli + /* ", milliseconds, " */", nanoseconds, value: "
                + (isCounter ? sharedCounter.getValue() : matMult
                        .getResultAsStringBuf()) + "\n";
        System.out.print(msg);
        StringBuffer entrLeftBuf = (isCounter ? sharedCounter.getBuf()
                : matMult.getResultAsStringBuf());
        entrLeftBuf.append(msg);
        
      //  String filenameSepcific = "_20bytes_";// XXX just for debugging for now
        
        FileUtils.saveToFile(barrierType.toString(), entrLeftBuf, false);
        return entrLeftBuf.toString();
    }

    /**
     * @param barrierType
     * @return New instance of barrier of the given type
     */
    public static Barrier getBarrier(BarrierType barrierType, int numOfThreads) {
        switch (barrierType) {
        case simpleBarrier:
            return new SimpleBarrier(numOfThreads);
        case senseBarrier:
            return new SenseBarrier(numOfThreads);
        case treeBarrier:
            return new TreeBarrier(numOfThreads, 2);
        case staticTreeBarrier:
            return new StaticTreeBarrier(numOfThreads, 3);
        case binaryStaticTreeBarrier:
            return new BinaryStaticTreeBarrier(numOfThreads);
        case lockBarrier:
            return new LockBarrier(numOfThreads);
        case cyclicBarrier:
            return new CyclicBarrierImpl(numOfThreads);
        case senseBarrierWithWait:
            return new SenseWithWaitBarrier(numOfThreads);
        case linearSenseBarrier:
            return new LinearSenseBarrier(numOfThreads);
        case linearSenseBarrierVolatile:
            return new LinearSenseBarrierVolatile(numOfThreads);
        case linearSenseBarrierVolatileWithBackoff:
            return new LinearSenseBarrierVolatileWithBackoff(numOfThreads);
        case splittedSenseBarrier:
            return new SplittedSenseBarrier(numOfThreads);
        default:
            throw new IllegalArgumentException("Got unsupported BarrierType: "
                    + barrierType);
        }
    }
    
    /**
     * @return matrices to multiply.
     */
    @SuppressWarnings("unused")
    private static ArrayList<double[][]> loadMatrices() {
        double[][] w = { { 3.0, 2. }, { 5., 1.0 } };
        System.out.println(w[0][0] + " " + w[0][1] + "\n" + w[1][0] + " " + w[1][1]);
        double[][] x = { { 1.0, 0. }, { 0., 1.0 } };
        System.out.println(x[0][0] + " " + x[0][1] + "\n" + x[1][0] + " " + x[1][1]);
        double[][] y = { { 4.0, 4. }, { 0., 1.0 } };
        System.out.println(y[0][0] + " " + y[0][1] + "\n" + y[1][0] + " " + y[1][1]);
        double[][] z = { { 3.0, 0. }, { 3., 1.0 } };
        System.out.println(z[0][0] + " " + z[0][1] + "\n" + z[1][0] + " " + z[1][1]);
        ArrayList<double[][]> mat = new ArrayList<double[][]>();
        mat.add(x);
        mat.add(y);
        mat.add(z);
        mat.add(w);
        return mat;
    }
}
